<!-- 只包含SCRCPY画面的简化组件 -->
<template>
  <div class="scrcpy-screen"   
    @dragover.prevent
    @dragenter="onScanDragEnter"
    @dragleave="onScanDragLeave"
    @drop.prevent="onScanDrop">
    <!-- 🆕 主容器：上下分栏布局 -->
    <div class="main-containers">
      <div class="loading-box" v-if="loading && connectionStatus != 'disconnected' && connectionStatus != 'error'">
        <LoadingComponent></LoadingComponent>
      </div>
      <!-- 上方：视频显示区域（黑色，减少左右边距） -->
      <div class="video-section">

        <div ref="videoContainer" class="video-container">
          <!-- 视频元素将由JMuxer动态创建 -->
        </div>

        <!-- 连接状态叠加层 -->
        <div v-if="connectionStatus !== 'connected'" class="status-overlay">
          <div v-if="connectionStatus === 'connecting'" class="connecting">
            <div class="loading-spinner"></div>
            <p>正在连接云手机...</p>
          </div>
          <div v-else-if="connectionStatus === 'disconnected'" class="disconnected" @click="connectWebSocket">
            <div class="play-icon">▶️</div>
            <p>点击开始连接</p>
          </div>
          <div v-else-if="connectionStatus === 'error'" class="error" @click="websocketError">
            <div class="error-icon">❌</div>
            <p>连接失败，点击重试</p>
          </div>
        </div>
      </div>

      <!-- 下方：控制面板（白色边框区域）v-if="shouldShowControlPanel" -->
      <div v-if="shouldShowControlPanel && admin" class="control-bottom-panel">
      
        <div class="control-section scan-upload-section" :class="{ 'drag-over': isScanDragOver }" @dragover.prevent
          @dragenter="onScanDragEnter" @dragleave="onScanDragLeave" @drop.prevent="onScanDrop">
          <div class="control-buttons-vertical">
            <img slot="reference" class="imgs" @click="sendHomeKey" src="@/assets/img/yun-home.png" alt="Home键"
              title="Home键">
            <img class="imgs" @click="sendBackKey" src="@/assets/img/back.png" alt="" title="返回键">
            <img class="imgs" @click="takeAndDownloadScreenshot" src="@/assets/img/jietu.png" alt="" title="截图下载">
            <div class="scan-box">
              <img class="imgs" @click="triggerScanUpload" src="@/assets/img/scan.png" alt="" title="上传二维码">
              <el-popover width="700" trigger="hover" content="这是一段内容,这是一段内容,这是一段内容,这是一段内容。">
                <img class="scan-tips-cont" src="@/assets/img/scan-tips-cont.png" alt="">
                <img class="scan-tips" slot="reference" src="@/assets/img/scan-tips.png" alt="">

              </el-popover>
            </div>



          
          </div>
          </div>
        </div>

        <!-- 🔧 截图工具 -->
        <!-- <div class="control-section"> -->
        <!-- <div class="control-buttons-vertical"> -->
        <!-- <img class="imgs" @click="takeAndDownloadScreenshot" src="@/assets/img/jietu.png" alt=""> -->
        <!-- <button @click="takeAndDownloadScreenshot" class="control-btn-small screenshot-btn"
                title="截图下载">
          📸
        </button> -->
        <!-- 🆕 扫码（上传图片并写入摄像头） -->
        <!-- <button @click="triggerScanUpload" class="control-btn-small" title="扫码(上传二维码)">
          <svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor" aria-hidden="true">
            <rect x="3" y="3" width="6" height="6" rx="1"></rect>
            <rect x="15" y="3" width="6" height="6" rx="1"></rect>
            <rect x="3" y="15" width="6" height="6" rx="1"></rect>
            <rect x="11" y="11" width="2" height="2"></rect>
            <rect x="15" y="15" width="2" height="2"></rect>
            <rect x="19" y="15" width="2" height="2"></rect>
            <rect x="15" y="19" width="6" height="2"></rect>
          </svg>
        </button> -->
        <!-- <div class="screenshot-info" v-if="lastScreenshotInfo">
          <small>{{ lastScreenshotInfo.size }}</small>
        </div> -->
        <!-- </div>
      </div> -->

        <!-- 🚫 移除剪贴板操作
        <div class="control-section">
          <div class="control-buttons-vertical">
            <button @click="handleCopy" class="control-btn-small copy-btn" title="复制 (Ctrl+C)">
              📋
            </button>
            <button @click="handlePaste" class="control-btn-small paste-btn" title="粘贴 (Ctrl+V)">
              📥
            </button>
          </div>
        </div> -->
       

        <!-- 🚫 移除文本输入
        <div class="control-section">
          <div class="text-input-vertical">
            <textarea
              v-model="manualInputText"
              @keyup.ctrl.enter="sendManualText"
              @focus="onInputFocus"
              @blur="onInputBlur"
              placeholder="输入文本Ctrl+Enter发送"
              class="manual-textarea-small"
              rows="3"
            ></textarea>
            <button @click="sendManualText" class="control-btn-small send-btn" title="发送文本">
              📤
            </button>
          </div>
        </div>
        -->
      <!-- </div> -->
    </div>
  </div>

  
</template>

<script>
import InteractionHandler from '@/utils/yunji/InteractionHandler'
import { convertImageToPNG } from '../../../../utils/makeImgs-utils/getUrlImg'

// JavaScript keyCode 到 Android KEYCODE 的映射表（参考 cc-y）
const KEY_CODE_MAP = {
  // 字母键 A-Z
  65: 29, 66: 30, 67: 31, 68: 32, 69: 33, 70: 34, 71: 35, 72: 36,
  73: 37, 74: 38, 75: 39, 76: 40, 77: 41, 78: 42, 79: 43, 80: 44,
  81: 45, 82: 46, 83: 47, 84: 48, 85: 49, 86: 50, 87: 51, 88: 52,
  89: 53, 90: 54,

  // 数字键 0-9
  48: 7, 49: 8, 50: 9, 51: 10, 52: 11, 53: 12, 54: 13, 55: 14, 56: 15, 57: 16,

  // 特殊键
  8: 67,   // Backspace -> AKEYCODE_DEL
  9: 61,   // Tab -> AKEYCODE_TAB
  13: 66,  // Enter -> AKEYCODE_ENTER
  16: 59,  // Shift -> AKEYCODE_SHIFT_LEFT
  17: 113, // Ctrl -> AKEYCODE_CTRL_LEFT
  18: 57,  // Alt -> AKEYCODE_ALT_LEFT
  27: 111, // Escape -> AKEYCODE_ESCAPE
  32: 62,  // Space -> AKEYCODE_SPACE
  46: 112, // Delete -> AKEYCODE_FORWARD_DEL

  // 方向键
  37: 21, // Left -> AKEYCODE_DPAD_LEFT
  38: 19, // Up -> AKEYCODE_DPAD_UP
  39: 22, // Right -> AKEYCODE_DPAD_RIGHT
  40: 20, // Down -> AKEYCODE_DPAD_DOWN

  // F功能键
  112: 131, 113: 132, 114: 133, 115: 134, 116: 135, 117: 136,
  118: 137, 119: 138, 120: 139, 121: 140, 122: 141, 123: 142,

  // 标点符号
  186: 74, // ; -> AKEYCODE_SEMICOLON
  187: 70, // = -> AKEYCODE_EQUALS
  188: 55, // , -> AKEYCODE_COMMA
  189: 69, // - -> AKEYCODE_MINUS
  190: 56, // . -> AKEYCODE_PERIOD
  191: 76, // / -> AKEYCODE_SLASH
  192: 68, // ` -> AKEYCODE_GRAVE
  219: 71, // [ -> AKEYCODE_LEFT_BRACKET
  220: 73, // \ -> AKEYCODE_BACKSLASH
  221: 72, // ] -> AKEYCODE_RIGHT_BRACKET
  222: 75, // ' -> AKEYCODE_APOSTROPHE

  // 数字键盘（NumLock 开启时）
  96: 144, 97: 145, 98: 146, 99: 147, 100: 148, 101: 149,
  102: 150, 103: 151, 104: 152, 105: 153,
  106: 155, // * -> AKEYCODE_NUMPAD_MULTIPLY
  107: 157, // + -> AKEYCODE_NUMPAD_ADD
  109: 156, // - -> AKEYCODE_NUMPAD_SUBTRACT
  110: 158, // . -> AKEYCODE_NUMPAD_DOT
  111: 154, // / -> AKEYCODE_NUMPAD_DIVIDE

  // 🔧 小键盘（NumLock 关闭时，某些浏览器/键盘报告不同的 keyCode）
  12: 149,  // Numpad 5 (Clear) -> AKEYCODE_NUMPAD_5 或忽略

  // 其他常用键
  20: 115,  // Caps Lock -> AKEYCODE_CAPS_LOCK
  33: 92,   // Page Up -> AKEYCODE_PAGE_UP
  34: 93,   // Page Down -> AKEYCODE_PAGE_DOWN
  35: 123,  // End -> AKEYCODE_MOVE_END
  36: 122,  // Home -> AKEYCODE_MOVE_HOME
  45: 124,  // Insert -> AKEYCODE_INSERT
  144: 143, // Num Lock -> AKEYCODE_NUM_LOCK

  // Meta 键（Windows/Command 键）
  91: 117,  // Left Meta -> AKEYCODE_META_LEFT
  92: 118,  // Right Meta -> AKEYCODE_META_RIGHT
  93: 82,   // Context Menu -> AKEYCODE_MENU
}

import { scanCode ,  scanAuto , yunjiClose} from '@/api/tenantApis/cloudMachine'
import LoadingComponent from '../../../indexComponents/LoadingComponent.vue'
export default {
  name: 'ScrcpyScreen',
  components: {
    LoadingComponent,

  },
  props: {
    admin:{
          type: Boolean,
      required: false,
      default: false
    },
    phoneId: {
      type: [String, Number],
      required: false,
      default: ''
    },
    deviceId: {
      type: String,
      default: ''
    },
    accountId:{
       type: [String, Number],
      default: ''
    },
    // 新增：直接传入后端生成的签名WSS地址（完整或相对路径）
    wssUrl: {
      type: String,
      default: ''
    }
  },
  data() {
    return {

      isInputFocused: false,
      loading: false,

      // WebSocket 连接
      websocket: null,
      connectionStatus: 'disconnected',
      reconnectAttempts: 0,
      maxReconnectAttempts: 3,
      shouldAutoReconnect: true,

      // 定时器
      iFrameTimer: null,
      keepaliveTimer: null,
      stallTimer: null,
      recordingStatusTimer: null,

      // WebCodecs 解码器
      wcDecoder: null,
      wcCanvas: null,
      wcCtx: null,
      decoderConfigured: false,
      codecDescription: null,

      // 帧状态
      lastFrameTime: 0,
      hasReceivedAnyFrame: false,
      wsOpenAt: 0,
      renderGateActive: false,

      // 设备信息
      _deviceSize: { w: 720, h: 1280 },
      readyInfo: null,

      // 交互处理器
      interactionHandler: null,
      renderElement: null,

      // 录制状态
      recording: false,
      pendingCmds: {},

      // 截图相关
      lastScreenshotInfo: null,
      screenshotCanvas: null,

      // 扫码上传
      hiddenFileInput: null,
      isScanDragOver: false,

      // 🆕 组件激活状态（多窗口焦点管理）
      isComponentActive: false,

      // 页面可见性
      isPageHidden: false,

      // 警告标志
      _warnedNoIdrAfterConfig: false,
      _warnedWaitingIdr: false,
      _lastToastAt: {},
      _numLockWarned: false, // 🆕 NumLock 提示标志
    }
  },
  computed: {
    // 🔧 控制面板显示逻辑，增加调试信息
    shouldShowControlPanel() {
      return this.connectionStatus === 'connected'
    }
  },

  async mounted() {
    console.log('🎬 ScrcpyScreen组件已挂载')

    try {
      // 🆕 先确保DOM已渲染，refs 可用
      await this.$nextTick()

      // 🆕 异步初始化播放器
      await this.initializePlayer()

      // 🆕 设置全局键盘事件监听（参考cc-y）
      document.addEventListener('keydown', this.handleKeyDown)
      document.addEventListener('keyup', this.handleKeyUp)
      // console.log('⌨️ 全局键盘事件监听器已添加')

      // 🆕 添加窗口大小改变监听器
      window.addEventListener('resize', this.handleWindowResize)
      // console.log('📏 窗口大小改变监听器已添加')

      // 🔧 添加页面可见性变化监听，解决后台切换回来画面不更新的问题
      document.addEventListener('visibilitychange', this.handleVisibilityChange)
      // console.log('👁️ 页面可见性监听器已添加')

      // 🔧 监听其他组件的激活事件，取消自己的激活状态
      window.addEventListener('scrcpy-component-activated', this.handleOtherComponentActivated)
      // console.log('🔔 组件激活事件监听器已添加')

      // console.log('✅ Player初始化完成')

      // 🆕 自动开始连接WebSocket
      setTimeout(() => {
        // console.log('🌐 准备连接WebSocket:', {
        //   connectionStatus: this.connectionStatus,
        //   deviceId: this.deviceId
        // })
        this.connectWebSocket()
      }, 100)

      // 🆕 启动无帧检测与自愈定时器
      try {
        if (this.stallTimer) {
          clearInterval(this.stallTimer)
        }
        this.stallTimer = setInterval(() => {
          try {
            const now = Date.now()
            const noFrameMs = now - (this.lastFrameTime || 0)
            if (this.isPageHidden) return
            if (this.connectionStatus !== 'connected' || !this.websocket) return
            const connectedDuration = now - (this.wsOpenAt || 0)
            // 读取动态阈值（若无则使用更宽松的默认：warn=30s, reconnect=45s, request=5s）
            const t = this._thresholds || { warnMs: 300000, reconnectMs: 450000, requestCfgMs: 5000 }
            // 冷启动宽限期：首次连接后若尚未收到任何帧，不触发断开，仅请求配置帧
            if (!this.hasReceivedAnyFrame) {
              if (connectedDuration > Math.min(4000, t.requestCfgMs)) this.requestConfigFrames()
              return
            }
            // 已经收过帧：正常卡流检测（动态阈值）
            if (noFrameMs > t.warnMs) {
              // 告警：请求配置帧并提示
              this.requestConfigFrames()
              this._toastOnce('noFrameWarn', '长时间无画面更新，正在尝试恢复...', 'warning', 12000)
            } else if (noFrameMs > t.requestCfgMs) {
              // 静默请求配置帧
              this.requestConfigFrames()
            }
            if (noFrameMs > t.reconnectMs && this.websocket && this.websocket.readyState === WebSocket.OPEN) {
              try { this.websocket.close(4000, 'stall timeout') } catch (e) { }
              this._toastOnce('reconnectByStall', '画面长时间停滞，正在重连...', 'warning', 15000)
            }
          } catch (e) {
            // 忽略定时器内部错误，避免影响主流程
          }
        }, 1000)
        // console.log('⏰ 无帧检测定时器已启动（每1秒）')
      } catch (e) {
        // console.warn('⚠️ 启动无帧检测定时器失败:', e)
      }

    } catch (error) {
      console.error('❌ 组件初始化失败:', error)
      this.connectionStatus = 'error'
    }

    // 🎥 启动录制状态轮询（兜底同步）
    try {
      if (this.recordingStatusTimer) clearInterval(this.recordingStatusTimer)
      this.recordingStatusTimer = setInterval(() => {
        try { this.fetchRecordingStatus() } catch (_) { }
      }, 5000)
    } catch (_) { }
  },

  beforeDestroy() {
    // console.log('🧹 清理ScrcpyScreen组件资源')
    this.cleanup()
    // 🆕 清理无帧检测定时器
    try { if (this.stallTimer) { clearInterval(this.stallTimer); this.stallTimer = null } } catch (e) { }

    // 🆕 移除窗口大小改变监听器
    window.removeEventListener('resize', this.handleWindowResize)
    // console.log('📏 窗口大小改变监听器已移除')

    // 🔧 移除页面可见性变化监听器
    document.removeEventListener('visibilitychange', this.handleVisibilityChange)
    // console.log('👁️ 页面可见性监听器已移除')

    // 🔧 移除组件激活事件监听器
    window.removeEventListener('scrcpy-component-activated', this.handleOtherComponentActivated)
    // console.log('🔔 组件激活事件监听器已移除')

    this.connectionStatus = 'disconnected'
  },
  methods: {
    websocketError() {
      this.$emit('reconts');
    },
    async initializePlayer() {
      try {
        await this.initWebCodecsIfAvailable()
        this.setupRenderSurface()
      } catch (error) {
        // console.error('Failed to initialize player:', error)
        this.connectionStatus = 'error'
      }
    },
    setupRenderSurface() {
      const container = this.$refs.videoContainer
      if (!container) return
      let renderElement = container.querySelector('canvas')
      if (!renderElement) {
        renderElement = document.createElement('canvas')
        renderElement.width = container.clientWidth || 640
        renderElement.height = container.clientHeight || 360
        renderElement.style.width = '100%'
        renderElement.style.height = '100%'
        renderElement.style.background = '#000'
        container.appendChild(renderElement)
      }
      this.renderElement = renderElement
      this.wcCanvas = renderElement
      this.wcCtx = renderElement.getContext('2d')

      // 🔧 添加点击事件激活当前组件
      renderElement.addEventListener('click', this.activateComponent)
      renderElement.addEventListener('mousedown', this.activateComponent)
      renderElement.addEventListener('touchstart', this.activateComponent)

      // 使用统一交互处理器，避免重复封包与事件冲突
      try {
        if (!this.interactionHandler) {
          const sendBuffer = (buf) => {
            try {
              if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
                this.websocket.send(buf)
              }
            } catch (_) { }
          }
          this.interactionHandler = new InteractionHandler({
            element: renderElement,
            deviceWidth: (this._deviceSize && this._deviceSize.w) || 720,
            deviceHeight: (this._deviceSize && this._deviceSize.h) || 1280,
            sendBuffer,
            enableDebug: true,
            enableWheel: false,
          })
        }
      } catch (_) { }
    },


    // 🆕 处理WebSocket消息 - 仅支持 Annex-B 格式
    handleWebSocketMessage(event) {
      try {
        // 📦 处理二进制数据（Annex-B H.264 流）
        if (event.data instanceof ArrayBuffer) {
          const u8 = new Uint8Array(event.data)
          // console.log(`📦 收到 Annex-B 数据: ${u8.length} 字节`)

          // 直接解码 Annex-B 格式
          this.feedWebCodecs(u8)
          this.lastFrameTime = Date.now()

          // 第一帧到达，切换状态
          if (!this.hasReceivedAnyFrame) {
            this.hasReceivedAnyFrame = true
            if (this.connectionStatus === 'connecting') {
              this.connectionStatus = 'connected'
              console.log('🎬 第一帧已到达，切换到已连接状态')
              if (this._firstFrameTimeout) {
                clearTimeout(this._firstFrameTimeout)
                this._firstFrameTimeout = null
              }
            }
          }
          return
        }

        // 📝 处理文本消息
        const message = JSON.parse(event.data)

        if (message.type === 'ready') {
          // 设备就绪消息
          this.readyInfo = message.data || null
          try {
            if (this.readyInfo && this.readyInfo.width && this.readyInfo.height) {
              this._deviceSize = { w: this.readyInfo.width >>> 0, h: this.readyInfo.height >>> 0 }
              if (this.interactionHandler) {
                this.interactionHandler.setDeviceSize(this._deviceSize.w, this._deviceSize.h)
              }
            }
          } catch (_) { }
          this.$emit && this.$emit('ws-ready', message.data || {})
        } else if (message.type === 'device_info') {
          // 设备信息
          this.$emit && this.$emit('ws-device-info', message)
        } else if (message.type === 'error') {
          // 错误消息
          this.$emit && this.$emit('ws-error', message)
          this.connectionStatus = 'error'
        } else if (message.type === 'keepalive') {
          // 保活消息
          if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
            this.websocket.send(JSON.stringify({ type: 'keepalive_response', timestamp: Date.now() }))
          }
        }
      } catch (error) {
        console.error('Error handling WebSocket message:', error)
      }
    },

    // 🆕 初始化ScrcpyInput控制器
    initializeInput(deviceWidth, deviceHeight) {
      try {
        if (!window.ScrcpyInput) {
          console.error('❌ ScrcpyInput未加载')
          return
        }

        // 🆕 防御：容器未就绪时延后重试
        const container = this.$refs.videoContainer
        if (!container || !document.body.contains(container)) {
          console.warn('⚠️ videoContainer 未准备好，延后初始化ScrcpyInput')
          if (this._initInputRetryCount < 10) {
            this._initInputRetryCount++
            setTimeout(() => {
              try { this.initializeInput(deviceWidth, deviceHeight) } catch (e) { }
            }, 200)
          } else {
            console.warn('⚠️ 多次重试后仍未就绪，放弃初始化ScrcpyInput')
          }
          return
        }

        // 🆕 获取/创建视频元素
        let renderElement = container.querySelector('canvas')
        if (!renderElement) {
          renderElement = document.createElement('canvas')
          renderElement.width = container.clientWidth || 640
          renderElement.height = container.clientHeight || 360
          renderElement.style.width = '100%'
          renderElement.style.height = '100%'
          renderElement.style.background = '#000'
          container.appendChild(renderElement)
        }
        this.renderElement = renderElement

        // 🆕 等待video元素加载完成并获得正确尺寸
        const checkReady = () => {
          if (!this.$refs || !this.$refs.videoContainer) return
          const rect = renderElement.getBoundingClientRect()
          if (rect.width > 0 && rect.height > 0) {
            const displayWidth = rect.width
            const displayHeight = rect.height

            // console.log('🎮 ScrcpyInput初始化参数:', {
            //   设备分辨率: { width: deviceWidth, height: deviceHeight },
            //   video原始尺寸: { width: renderElement.width, height: renderElement.height },
            //   video显示尺寸: { width: displayWidth, height: displayHeight },
            //   video元素尺寸: { 
            //     clientWidth: renderElement.clientWidth,
            //     clientHeight: renderElement.clientHeight,
            //     offsetWidth: renderElement.offsetWidth,
            //     offsetHeight: renderElement.offsetHeight
            //   }
            // })

            // 🆕 确保video元素可以接收键盘事件
            renderElement.tabIndex = 0
            renderElement.style.outline = 'none'
            renderElement.setAttribute('contenteditable', 'false')

            // 🆕 清理可能存在的旧ScrcpyInput实例
            if (this.scrcpyInput) {
              console.log('🧹 清理旧的ScrcpyInput实例')
              if (typeof this.scrcpyInput.cleanup === 'function') {
                this.scrcpyInput.cleanup()
              }
              this.scrcpyInput = null
            }

            // 🆕 使用设备的实际分辨率创建ScrcpyInput
            this.scrcpyInput = new window.ScrcpyInput(
              (data) => {
                // 回调函数：将二进制控制数据发送到WebSocket
                try {
                  if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
                    // 🆕 检查是否为系统键
                    let isSystemKey = false
                    if (data.byteLength >= 6) {
                      const view = new DataView(data)
                      const msgType = view.getUint8(0)
                      const action = view.getUint8(1)

                      if (msgType === 0) { // 键盘事件
                        const keycode = view.getInt32(2, false) // 大端序
                        isSystemKey = [3, 4, 82].includes(keycode) // HOME, BACK, MENU

                        if (isSystemKey) {
                          console.log('🔑 ScrcpyInput发送系统键:', {
                            keycode: keycode,
                            action: action === 0 ? 'DOWN' : 'UP'
                          })
                        }
                      }
                    }

                    this.websocket.send(data)  // 直接发送二进制数据
                    // console.log('📤 发送二进制控制数据:', data.byteLength, '字节', isSystemKey ? '(系统键)' : '')

                    // 🆕 系统键发送后设置短暂的连接保护
                    if (isSystemKey) {
                      this.systemKeyProtection = true
                      setTimeout(() => {
                        this.systemKeyProtection = false
                      }, 2000) // 2秒保护期
                    }

                  } else {
                    console.warn('⚠️ WebSocket未连接，无法发送控制数据，状态:', this.websocket?.readyState)

                    // 🆕 如果WebSocket断开，尝试重连
                    if (this.websocket?.readyState === WebSocket.CLOSED && this.shouldAutoReconnect) {
                      // console.log('🔄 检测到WebSocket断开，尝试重新连接...')
                      setTimeout(() => {
                        this.connectWebSocket()
                      }, 1000)
                    }
                  }
                } catch (error) {
                  console.error('❌ 发送控制数据时出错:', error)

                  // 🆕 如果是网络错误，标记需要重连
                  if (error.name === 'NetworkError' || error.message.includes('network')) {
                    console.log('🔄 网络错误，标记需要重连')
                    this.connectionStatus = 'disconnected'
                  }
                }
              },
              renderElement,
              deviceWidth,   // 使用设备的实际分辨率
              deviceHeight,  // 使用设备的实际分辨率
              true  // debug = true
            )

            // console.log('✅ ScrcpyInput控制器初始化完成:', deviceWidth, 'x', deviceHeight)
            this._initInputRetryCount = 0

            // 🆕 立即聚焦video元素以接收键盘事件
            renderElement.focus()
            // console.log('🎯 Video元素已立即获得焦点')

            // 🆕 添加点击事件确保焦点
            renderElement.addEventListener('click', () => {
              renderElement.focus()
              // console.log('🎯 点击后重新获得焦点')
            })

            // 🆕 绑定 Ctrl+V 粘贴（浏览器→设备）与 Ctrl+C 提示
            try {
              if (this._videoKeyHandler) {
                try { renderElement.removeEventListener('keydown', this._videoKeyHandler) } catch (_) { }
              }
              if (this._videoPasteHandler) {
                try { renderElement.removeEventListener('paste', this._videoPasteHandler) } catch (_) { }
              }

              this._videoKeyHandler = (ev) => {
                try {
                  const key = (ev.key || '').toLowerCase()
                  const metaOrCtrl = ev.ctrlKey || ev.metaKey
                  if (metaOrCtrl && key === 'v') {
                    // 交给 paste 事件处理，先阻止默认
                    ev.preventDefault()
                    // 若浏览器未触发 paste 事件，兜底读取剪贴板
                    if (navigator.clipboard && navigator.clipboard.readText) {
                      navigator.clipboard.readText().then((text) => {
                        if (!text) return
                        if (!this._guardReadOnly(() => true)) return
                        const v = text.length > 10000 ? text.slice(0, 10000) : text
                        // 仅走 ScrcpyInput 二进制：设置剪贴板并粘贴
                        if (this.scrcpyInput && typeof this.scrcpyInput.setClipboard === 'function') {
                          try {
                            this.scrcpyInput.setClipboard(v, true)
                            this.$message && this.$message.success('已粘贴到设备')
                            return
                          } catch (_) { }
                        }
                      }).catch(() => { })
                    }
                  } else if (metaOrCtrl && key === 'c') {
                    // 设备→浏览器剪贴板需后端支持，当前仅提示
                    this._toastOnce('copy-not-supported', '暂不支持从设备复制到本机剪贴板', 'warning', 8000)
                  }
                } catch (_) { }
              }

              this._videoPasteHandler = (e) => {
                try {
                  const text = (e.clipboardData && e.clipboardData.getData && e.clipboardData.getData('text')) || ''
                  if (!text) return
                  e.preventDefault()
                  if (!this._guardReadOnly(() => true)) return
                  const v = text.length > 10000 ? text.slice(0, 10000) : text
                  // 仅走 ScrcpyInput 二进制剪贴板
                  if (this.scrcpyInput && typeof this.scrcpyInput.setClipboard === 'function') {
                    try {
                      this.scrcpyInput.setClipboard(v, true)
                      this.$message && this.$message.success('已粘贴到设备')
                      return
                    } catch (_) { }
                  }
                } catch (_) { }
              }

              renderElement.addEventListener('keydown', this._videoKeyHandler)
              renderElement.addEventListener('paste', this._videoPasteHandler)
              console.log('🧩 已绑定 Ctrl+V 粘贴与 Ctrl+C 提示到 video 元素')
            } catch (_) { }

          } else {
            // video还没准备好，继续等待
            console.log('⏳ 等待video元素加载完成...')
            setTimeout(checkReady, 100)
          }
        }

        // 开始检查video是否准备好
        checkReady()

      } catch (error) {
        console.error('❌ ScrcpyInput初始化失败:', error)
      }
    },

    // 🆕 测试ScrcpyInput功能
    testScrcpyInputFunctionality() {
      // console.log('🧪 开始测试ScrcpyInput功能...')

      const renderElement = this.renderElement

      // 测试1: 检查video元素状态
      // console.log('📹 Render元素状态:', {
      //   存在: !!renderElement,
      //   tagName: renderElement?.tagName,
      //   tabIndex: renderElement?.tabIndex,
      //   获得焦点: document.activeElement === renderElement,
      //   位置: renderElement?.getBoundingClientRect()
      // })

      // 测试2: 检查ScrcpyInput实例
      // console.log('🎮 ScrcpyInput实例状态:', {
      //   存在: !!this.scrcpyInput,
      //   类型: typeof this.scrcpyInput,
      //   回调函数: typeof this.scrcpyInput?.callback,
      //   宽度: this.scrcpyInput?.width,
      //   高度: this.scrcpyInput?.height,
      //   调试模式: this.scrcpyInput?.debug
      // })

      // 测试3: 检查事件监听器（通过模拟事件）
      if (renderElement && this.scrcpyInput) {
        console.log('⌨️ 模拟键盘事件测试...')

        // 创建模拟事件
        const testEvent = new KeyboardEvent('keydown', {
          key: 'a',
          code: 'KeyA',
          bubbles: true,
          cancelable: true
        })

        // 分发事件
        renderElement.dispatchEvent(testEvent)
        console.log('✅ 模拟按键事件已发送')
      }

      // 测试4: WebSocket连接状态
      // console.log('🌐 WebSocket状态:', {
      //   存在: !!this.websocket,
      //   状态: this.websocket?.readyState,
      //   状态名称: this.websocket?.readyState === 0 ? 'CONNECTING' : 
      //             this.websocket?.readyState === 1 ? 'OPEN' :
      //             this.websocket?.readyState === 2 ? 'CLOSING' : 
      //             this.websocket?.readyState === 3 ? 'CLOSED' : 'UNKNOWN'
      // })

      // 测试5: 剪贴板API可用性检查
      const clipboardSupported = this.scrcpyInput?.isClipboardAPISupported?.() ||
        (navigator.clipboard &&
          typeof navigator.clipboard.readText === 'function')
      // console.log('📋 剪贴板API状态:', {
      //   navigator_clipboard存在: !!navigator.clipboard,
      //   readText可用: typeof navigator?.clipboard?.readText === 'function',
      //   writeText可用: typeof navigator?.clipboard?.writeText === 'function',
      //   ScrcpyInput检查结果: clipboardSupported,
      //   当前协议: window.location.protocol,
      //   当前域名: window.location.hostname,
      //   推荐解决方案: !clipboardSupported ? '使用localhost或HTTPS访问' : '剪贴板功能正常'
      // })
    },

    // 🆕 获取输入框焦点 - 阻止自动滚动
    focusInput() {
      if (this.$refs.hiddenInput) {
        // 阻止浏览器自动滚动到输入框位置
        this.$refs.hiddenInput.focus({ preventScroll: true })
        console.log('🎯 隐藏输入框获得焦点（已阻止滚动）')
      }
    },

    // 🆕 输入框获得焦点
    onInputFocus() {
      this.isInputFocused = true
      console.log('🎯 输入框获得焦点，可以输入文字')
    },

    // 🆕 输入框失去焦点
    onInputBlur() {
      this.isInputFocused = false
      console.log('🎯 输入框失去焦点')
    },

    // 🆕 键盘按下事件（参考cc-y实现）
    handleKeyDown(event) {
      // 🔧 多窗口焦点管理：只有激活的组件才响应键盘
      if (!this.isComponentActive) {
        return
      }

      // 避免在输入框中触发
      if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') {
        return
      }

      // 检查WebSocket连接
      if (!this.websocket || this.websocket.readyState !== WebSocket.OPEN) {
        return
      }

      // 🆕 处理 Ctrl+V 粘贴
      if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'v') {
        event.preventDefault()
        this.handlePaste()
        return
      }

      // 🔧 调试：打印所有按键信息（临时）
      // 特别标记小键盘按键（location === 3）
      const isNumpad = event.location === 3 || (event.code && event.code.startsWith('Numpad'))
      if (isNumpad) {
        console.log('🔢 小键盘按键 DEBUG:', {
          key: event.key,
          code: event.code,
          keyCode: event.keyCode,
          location: event.location,
          numLock: event.getModifierState ? event.getModifierState('NumLock') : 'unknown'
        })
      } else {
        console.log('🔍 普通按键 DEBUG:', event.key, 'code:', event.code, 'keyCode:', event.keyCode)
      }

      // 映射 JavaScript keyCode 到 Android KEYCODE
      let androidKeycode = KEY_CODE_MAP[event.keyCode]

      // 🔧 小键盘：不依赖 NumLock，优先根据 event.key 判定
      if (event.code && event.code.startsWith('Numpad')) {
        const k = event.key
        // 数字：统一映射为顶排数字 KEYCODE_0~9（7~16），多数输入框更稳定
        if (/^[0-9]$/.test(k)) {
          androidKeycode = 7 + (k.charCodeAt(0) - 48)
          console.log('🔢 小键盘数字 (by key):', k, '→ Android:', androidKeycode)
        } else {
          // 方向/功能键按可读的 key 名称映射
          const byKeyMap = {
            ArrowUp: 19,
            ArrowDown: 20,
            ArrowLeft: 21,
            ArrowRight: 22,
            Home: 122,
            End: 123,
            PageUp: 92,
            PageDown: 93,
            Insert: 124,
            Delete: 112,
            Clear: 28 // KEYCODE_CLEAR
          }
          const mapped = byKeyMap[k]
          if (mapped !== undefined) {
            androidKeycode = mapped
            console.log('🔢 小键盘功能 (by key):', k, '→ Android:', androidKeycode)
          }
        }
      } else if (event.keyCode >= 96 && event.keyCode <= 105) {
        // Fallback: 直接按数字映射到顶排数字
        androidKeycode = 7 + (event.keyCode - 96)
        console.log('🔢 小键盘数字 (by keyCode):', event.keyCode, '→ Android:', androidKeycode)
      } else if (event.keyCode >= 106 && event.keyCode <= 111) {
        // 小键盘运算符：* + - . /
        androidKeycode = KEY_CODE_MAP[event.keyCode]
        console.log('🔢 小键盘运算符:', event.key, '→', androidKeycode)
      }

      if (androidKeycode === undefined) {
        console.warn('⚠️ 未映射的按键:', event.key, 'code:', event.code, 'JS keyCode:', event.keyCode, 'location:', event.location)
        return
      }

      // 阻止默认行为
      event.preventDefault()

      // 计算 Android metastate（修饰键状态）
      let metastate = 0
      if (event.shiftKey) metastate |= 0x01  // META_SHIFT_ON
      if (event.ctrlKey) metastate |= 0x1000  // META_CTRL_ON
      if (event.altKey) metastate |= 0x02  // META_ALT_ON
      if (event.metaKey) metastate |= 0x10000  // META_META_ON
      // 🆕 检测 Caps Lock 状态
      if (event.getModifierState && event.getModifierState('CapsLock')) {
        metastate |= 0x100000  // META_CAPS_LOCK_ON
      }

      // 构造二进制控制消息（官方scrcpy协议）
      const buf = new ArrayBuffer(14)
      const dv = new DataView(buf)
      dv.setUint8(0, 0)  // msg_type: inject_keycode
      dv.setUint8(1, 0)  // action: DOWN
      dv.setUint32(2, androidKeycode, false)  // Android KEYCODE
      dv.setUint32(6, 0, false)  // repeat
      dv.setUint32(10, metastate, false)  // metastate

      this.websocket.send(buf)
      const capsLock = event.getModifierState && event.getModifierState('CapsLock') ? '🔒' : ''
      console.log('⌨️ Key DOWN:', event.key, 'JS:', event.keyCode, '→ Android:', androidKeycode, 'meta:', metastate, capsLock)
    },

    // 🆕 键盘释放事件（参考cc-y实现）
    handleKeyUp(event) {
      // 🔧 多窗口焦点管理：只有激活的组件才响应键盘
      if (!this.isComponentActive) {
        return
      }

      // 避免在输入框中触发
      if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') {
        return
      }

      // 检查WebSocket连接
      if (!this.websocket || this.websocket.readyState !== WebSocket.OPEN) {
        return
      }

      // 映射 JavaScript keyCode 到 Android KEYCODE
      let androidKeycode = KEY_CODE_MAP[event.keyCode]

      // 🔧 小键盘：不依赖 NumLock（与 keyDown 一致）
      if (event.code && event.code.startsWith('Numpad')) {
        const k = event.key
        if (/^[0-9]$/.test(k)) {
          androidKeycode = 7 + (k.charCodeAt(0) - 48)
        } else {
          const byKeyMap = {
            ArrowUp: 19,
            ArrowDown: 20,
            ArrowLeft: 21,
            ArrowRight: 22,
            Home: 122,
            End: 123,
            PageUp: 92,
            PageDown: 93,
            Insert: 124,
            Delete: 112,
            Clear: 28
          }
          const mapped = byKeyMap[k]
          if (mapped === undefined) {
            // 未识别则不发送
            androidKeycode = undefined
          } else {
            androidKeycode = mapped
          }
        }
      } else if (event.keyCode >= 96 && event.keyCode <= 105) {
        androidKeycode = 7 + (event.keyCode - 96)
      } else if (event.keyCode >= 106 && event.keyCode <= 111) {
        androidKeycode = KEY_CODE_MAP[event.keyCode]
      }

      if (androidKeycode === undefined) {
        return  // 静默忽略未映射的按键释放
      }

      // 阻止默认行为
      event.preventDefault()

      // 计算 Android metastate（修饰键状态）
      let metastate = 0
      if (event.shiftKey) metastate |= 0x01  // META_SHIFT_ON
      if (event.ctrlKey) metastate |= 0x1000  // META_CTRL_ON
      if (event.altKey) metastate |= 0x02  // META_ALT_ON
      if (event.metaKey) metastate |= 0x10000  // META_META_ON
      // 🆕 检测 Caps Lock 状态
      if (event.getModifierState && event.getModifierState('CapsLock')) {
        metastate |= 0x100000  // META_CAPS_LOCK_ON
      }

      // 构造二进制控制消息（官方scrcpy协议）
      const buf = new ArrayBuffer(14)
      const dv = new DataView(buf)
      dv.setUint8(0, 0)  // msg_type: inject_keycode
      dv.setUint8(1, 1)  // action: UP
      dv.setUint32(2, androidKeycode, false)  // Android KEYCODE
      dv.setUint32(6, 0, false)  // repeat
      dv.setUint32(10, metastate, false)  // metastate

      this.websocket.send(buf)
      const capsLock = event.getModifierState && event.getModifierState('CapsLock') ? '🔒' : ''
      console.log('⌨️ Key UP:', event.key, 'JS:', event.keyCode, '→ Android:', androidKeycode, 'meta:', metastate, capsLock)
    },

    // 🆕 处理粘贴（Ctrl+V）
    async handlePaste() {
      try {
        console.log('📋 触发粘贴操作', )
        
        // 检查剪贴板 API 是否可用
        if (!navigator.clipboard || !navigator.clipboard.readText) {
          console.warn('⚠️ 剪贴板 API 不可用（需要 HTTPS 或 localhost）')
          this.$message && this.$message.warning('剪贴板功能需要在 HTTPS 环境下使用')
          return
        }

        // 读取剪贴板内容
        const text = await navigator.clipboard.readText()
        if (!text || text.trim() === '') {
          console.log('📋 剪贴板为空')
          return
        }

        console.log('📋 读取到剪贴板内容:', text.substring(0, 50) + (text.length > 50 ? '...' : ''))

        // 发送文本到设备
        this.sendTextToDevice(text)

        this.$message && this.$message.success(`已粘贴 ${text.length} 个字符`)
      } catch (error) {
        console.error('❌ 粘贴失败:', error)
        if (error.name === 'NotAllowedError') {
          this.$message && this.$message.warning('请授予剪贴板访问权限')
        } else {
          this.$message && this.$message.error('粘贴失败: ' + error.message)
        }
      }
    },

    // 🆕 发送文本到设备（使用官方 scrcpy 文本注入协议）
    sendTextToDevice(text) {
      if (!this.websocket || this.websocket.readyState !== WebSocket.OPEN) {
        console.warn('⚠️ WebSocket 未连接')
        return
      }

      if (!text) {
        return
      }

      // 限制文本长度（避免过大）
      const maxLength = 10000
      const truncatedText = text.length > maxLength ? text.substring(0, maxLength) : text

      try {
        // scrcpy 文本注入协议：msg_type=1 (SC_CONTROL_MSG_TYPE_INJECT_TEXT)
        // 格式：type(1) + text_length(4BE) + text(UTF-8)
        const textBytes = new TextEncoder().encode(truncatedText)
        const buf = new ArrayBuffer(1 + 4 + textBytes.length)
        const dv = new DataView(buf)

        dv.setUint8(0, 1)  // msg_type: inject_text
        dv.setUint32(1, textBytes.length, false)  // text length (big-endian)

        // 写入文本字节
        const u8 = new Uint8Array(buf)
        u8.set(textBytes, 5)

        this.websocket.send(buf)
        console.log('📤 已发送文本到设备:', truncatedText.length, '字节')

        if (text.length > maxLength) {
          this.$message && this.$message.warning(`文本过长，已截断为 ${maxLength} 字符`)
        }
      } catch (error) {
        console.error('❌ 发送文本失败:', error)
        this.$message && this.$message.error('发送文本失败')
      }
    },

    // ===== WebCodecs 路径 =====
    async initWebCodecsIfAvailable() {
      try {
        const supported = ('VideoDecoder' in window)
        if (!supported) { this.useWebCodecs = false; return }
        const self = this
        this.wcDecoder = new VideoDecoder({
          output(frame) {
            try {
              if (!self.renderElement) {
                self.setupRenderSurface()
              }
              if (!self.renderElement) return
              if (!self.wcCtx) {
                self.wcCtx = self.renderElement.getContext('2d')
              }
              if (!self.wcCtx) return
              if (self.renderElement.width !== frame.displayWidth || self.renderElement.height !== frame.displayHeight) {
                self.renderElement.width = frame.displayWidth
                self.renderElement.height = frame.displayHeight
              }
              self.wcCtx.drawImage(frame, 0, 0)

              // 🔧 第一帧渲染成功后切换到已连接状态
              if (!self.hasReceivedAnyFrame) {
                self.hasReceivedAnyFrame = true
                if (self.connectionStatus === 'connecting') {
                  self.connectionStatus = 'connected'
                  console.log('🎬 第一帧已渲染，切换到已连接状态')
                  // 清理超时计时器
                  if (self._firstFrameTimeout) {
                    clearTimeout(self._firstFrameTimeout)
                    self._firstFrameTimeout = null
                  }
                }
              }
            } catch (_) { }
            try { frame.close() } catch { }
          },
          error(e) {
            console.error('WebCodecs decoder error:', e)
          }
        })
        this.useWebCodecs = true
        this.wcInitTs = performance.now()
      } catch (e) {
        console.warn('⚠️ WebCodecs 初始化失败:', e)
        try {
          if (this.wcDecoder) { this.wcDecoder.close() }
        } catch { }
        this.wcDecoder = null
        this.useWebCodecs = false
      }
    },

    _annexBToLengthPrefixed(annexBData) {
      // 将 Annex-B 格式（start code + NALU）转换为 length-prefixed 格式（4字节长度 + NALU）
      const nalus = []
      let i = 0

      while (i < annexBData.length) {
        // 查找起始码 (0x00000001 或 0x000001)
        if (annexBData[i] === 0 && annexBData[i + 1] === 0) {
          let startCodeLen = 0
          if (annexBData[i + 2] === 0 && annexBData[i + 3] === 1) {
            startCodeLen = 4  // 0x00000001
          } else if (annexBData[i + 2] === 1) {
            startCodeLen = 3  // 0x000001
          } else {
            i++
            continue
          }

          // 找到 NALU 起始位置
          const naluStart = i + startCodeLen

          // 查找下一个起始码
          let naluEnd = annexBData.length
          for (let j = naluStart; j < annexBData.length - 2; j++) {
            if (annexBData[j] === 0 && annexBData[j + 1] === 0 &&
              (annexBData[j + 2] === 1 || (annexBData[j + 2] === 0 && annexBData[j + 3] === 1))) {
              naluEnd = j
              break
            }
          }

          // 提取 NALU（不含起始码）
          const nalu = annexBData.subarray(naluStart, naluEnd)
          nalus.push(nalu)
          i = naluEnd
        } else {
          i++
        }
      }

      // 构建 length-prefixed 格式
      let totalLen = 0
      for (const nalu of nalus) {
        totalLen += 4 + nalu.length  // 4字节长度前缀 + NALU数据
      }

      const result = new Uint8Array(totalLen)
      let offset = 0
      for (const nalu of nalus) {
        // 写入 4 字节长度（big-endian）
        result[offset++] = (nalu.length >> 24) & 0xFF
        result[offset++] = (nalu.length >> 16) & 0xFF
        result[offset++] = (nalu.length >> 8) & 0xFF
        result[offset++] = nalu.length & 0xFF
        // 写入 NALU 数据
        result.set(nalu, offset)
        offset += nalu.length
      }

      return result
    },

    _buildAvcC(spsData, ppsData) {
      // 构建 AVCDecoderConfigurationRecord (avcC)
      // 格式: configurationVersion(1) + AVCProfileIndication(1) + profile_compatibility(1) + AVCLevelIndication(1) 
      //       + lengthSizeMinusOne(1) + numOfSequenceParameterSets(1) + SPS_length(2) + SPS_data 
      //       + numOfPictureParameterSets(1) + PPS_length(2) + PPS_data
      const spsLen = spsData.length
      const ppsLen = ppsData.length
      // 正确的长度：5字节头 + 1字节SPS计数 + 2字节SPS长度 + SPS数据 + 1字节PPS计数 + 2字节PPS长度 + PPS数据
      const totalLen = 5 + 1 + 2 + spsLen + 1 + 2 + ppsLen

      const avcC = new Uint8Array(totalLen)
      let offset = 0

      // configurationVersion = 1
      avcC[offset++] = 1
      // AVCProfileIndication, profile_compatibility, AVCLevelIndication (从 SPS 中提取)
      avcC[offset++] = spsData[1]  // profile_idc
      avcC[offset++] = spsData[2]  // constraint_set flags
      avcC[offset++] = spsData[3]  // level_idc
      // lengthSizeMinusOne (通常是 3，表示 NALU 长度用 4 字节)
      avcC[offset++] = 0xFF  // 6 bits reserved (111111) + 2 bits lengthSizeMinusOne (11 = 3)

      // numOfSequenceParameterSets (5 bits reserved + 3 bits count)
      avcC[offset++] = 0xE1  // 111 (reserved) + 00001 (1 SPS)
      // SPS length (big-endian 16-bit)
      avcC[offset++] = (spsLen >> 8) & 0xFF
      avcC[offset++] = spsLen & 0xFF
      // SPS data
      avcC.set(spsData, offset)
      offset += spsLen

      // numOfPictureParameterSets
      avcC[offset++] = 1  // 1 PPS
      // PPS length (big-endian 16-bit)
      avcC[offset++] = (ppsLen >> 8) & 0xFF
      avcC[offset++] = ppsLen & 0xFF
      // PPS data
      avcC.set(ppsData, offset)

      console.log(`📦 构建avcC: total=${totalLen}, SPS=${spsLen}, PPS=${ppsLen}, offset=${offset + ppsLen}`)
      return avcC
    },

    feedWebCodecs(uint8Data) {
      try {
        if (!this.wcDecoder || this.wcDecoder.state === 'closed') {
          console.warn('⚠️ WebCodecs解码器未就绪')
          return
        }

        // 解析 Annex-B NAL 单元，查找 SPS/PPS/IDR，并提取完整的 SPS/PPS 数据
        const NALU = { IDR: 5, SEI: 6, SPS: 7, PPS: 8 }
        let hasSps = false, hasPps = false, hasIdr = false, spsHead = null
        let spsData = null, ppsData = null

        for (let i = 0; i + 5 <= uint8Data.length;) {
          if (!(uint8Data[i] === 0 && uint8Data[i + 1] === 0 && uint8Data[i + 2] === 0 && uint8Data[i + 3] === 1)) {
            i++
            continue
          }

          const type = uint8Data[i + 4] & 31

          // 找下一个起始码
          let j = i + 4
          for (let k = i + 4; k + 4 <= uint8Data.length; k++) {
            if (uint8Data[k] === 0 && uint8Data[k + 1] === 0 && uint8Data[k + 2] === 0 && uint8Data[k + 3] === 1) {
              j = k
              break
            }
            if (k + 4 >= uint8Data.length) j = uint8Data.length
          }

          if (type === NALU.SPS) {
            hasSps = true
            if (!spsHead && (j - i) >= 8) {
              spsHead = uint8Data.subarray(i + 5, i + 8)
            }
            // 提取完整的 SPS（不含起始码）
            if (!spsData) {
              spsData = uint8Data.subarray(i + 4, j)
            }
          } else if (type === NALU.PPS) {
            hasPps = true
            // 提取完整的 PPS（不含起始码）
            if (!ppsData) {
              ppsData = uint8Data.subarray(i + 4, j)
            }
          } else if (type === NALU.IDR) {
            hasIdr = true
          }

          i = j
        }

        // 如果未配置且检测到 SPS/PPS，构建 avcC description 并配置解码器
        if (!this.decoderConfigured && spsData && ppsData && spsHead && spsHead.length === 3) {
          const codec = `avc1.${[...spsHead].map(v => v.toString(16).padStart(2, '0').toUpperCase()).join('')}`

          // 构建 avcC (AVCDecoderConfigurationRecord)
          const description = this._buildAvcC(spsData, ppsData)

          try {
            this.wcDecoder.configure({
              codec,
              description,  // 必须提供 description
              optimizeForLatency: true,
              hardwareAcceleration: 'prefer-hardware'
            })
            this.decoderConfigured = true
            this.codecDescription = description
            console.log('✅ WebCodecs配置成功（带avcC）:', codec, 'SPS=', spsData.length, 'PPS=', ppsData.length)

            // 配置后，将 Annex-B 转换为 length-prefixed 格式再解码
            const isKey = hasIdr || (hasSps && hasPps)
            const lengthPrefixed = this._annexBToLengthPrefixed(uint8Data)
            const timestamp = Math.floor(performance.now() * 1000)
            const chunk = new EncodedVideoChunk({
              type: isKey ? 'key' : 'delta',
              timestamp: timestamp,
              data: lengthPrefixed
            })
            this.wcDecoder.decode(chunk)
            console.log(`🎬 解码首帧（配置后）: ${isKey ? 'KEY' : 'DELTA'}, 原始=${uint8Data.length}, 转换后=${lengthPrefixed.length}`)

            if (isKey) {
              this.hasReceivedAnyFrame = true
            }
            return
          } catch (err) {
            // 配置后的第一帧不是 IDR 是正常情况，等待后续 IDR
            if (err.name === 'DataError' && err.message.includes('key frame is required')) {
              if (!this._warnedNoIdrAfterConfig) {
                console.warn('⚠️ 配置后等待 IDR 帧中...（这是正常的）')
                this._warnedNoIdrAfterConfig = true
              }
            } else {
              console.error('❌ WebCodecs配置失败:', err)
              // 配置失败，重置状态以便重试
              this.decoderConfigured = false
            }
            return  // 配置阶段出错，等待下一帧
          }
        }

        // 未配置时跳过解码
        if (!this.decoderConfigured) {
          console.warn('⚠️ 等待SPS配置解码器...')
          return
        }

        // 已配置：判断帧类型并解码（需要转换为 length-prefixed 格式）
        // 官方 scrcpy 的 P 帧可以独立解码（baseline profile），不严格要求首帧为 IDR
        const isKey = hasIdr || (hasSps && hasPps)  // SPS+PPS 也视为关键帧

        const lengthPrefixed = this._annexBToLengthPrefixed(uint8Data)
        const timestamp = Math.floor(performance.now() * 1000)
        const chunk = new EncodedVideoChunk({
          type: isKey ? 'key' : 'delta',
          timestamp: timestamp,
          data: lengthPrefixed
        })

        this.wcDecoder.decode(chunk)
        if (!this.hasReceivedAnyFrame && isKey) {
          this.hasReceivedAnyFrame = true
        }
        console.log(`📹 解码帧: ${isKey ? 'KEY' : 'DELTA'}, 原始=${uint8Data.length}, 转换后=${lengthPrefixed.length}`)
      } catch (error) {
        // 等待 IDR 帧是正常情况
        if (error.name === 'DataError' && error.message.includes('key frame is required')) {
          if (!this._warnedWaitingIdr) {
            console.warn('⚠️ 等待关键帧中...（正常）')
            this._warnedWaitingIdr = true
          }
          // 不重置 decoderConfigured，继续等待 IDR
        } else {
          console.error('🆘 WebCodecs解码错误:', error)
          // 只有在其他错误时才重置配置
          this.decoderConfigured = false
        }
      }
    },

    _containsIdrOrSps(bytes) {
      // 搜索起始码 00 00 00 01 或 00 00 01
      const len = bytes.length
      for (let i = 0; i < len - 4; i++) {
        const a = bytes[i], b = bytes[i + 1], c = bytes[i + 2], d = bytes[i + 3]
        let off = -1
        if (a === 0 && b === 0 && c === 0 && d === 1) off = i + 4
        else if (a === 0 && b === 0 && c === 1) off = i + 3
        if (off > 0 && off < len) {
          const nal = bytes[off] & 0x1f
          if (nal === 5 || nal === 7) return true
        }
      }
      return false
    },

    // Android系统键方法（通过 InteractionHandler 发送）
    sendHomeKey() {
      this.interactionHandler?.sendKeyDownUp(3)
    },

    sendBackKey() {
      this.interactionHandler?.sendKeyDownUp(4)
    },

    sendMenuKey() {
      this.interactionHandler?.sendKeyDownUp(82)
    },

    sendRecentAppsKey() {
      this.interactionHandler?.sendKeyDownUp(187)
    },

    async connectWebSocket() {
      console.log('🌐 开始连接WebSocket到设备:', this.deviceId)

      // 🆕 重置自动重连标志，允许新连接的自动重连
      this.shouldAutoReconnect = true
      this.reconnectAttempts = 0

      this.connectionStatus = 'connecting'

      // 🌐 使用相对路径，通过Vite代理连接WebSocket
      const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
      // 🔧 强制使用标准端口，避免18873端口问题
      const host = window.location.host  // 包含端口的完整主机地址
      let wsUrl = (this.wssUrl || '').trim()
      if (wsUrl) {
        if (wsUrl.startsWith('/')) {
          wsUrl = 'wss://d.bhhao.com' + wsUrl
        }
      } else {
        // 没有传入签名地址时，自动向后端申请签名WS地址
        try {
          // 使用全局axios，自动带上认证token；若 busy 则尝试抢占（preempt=true）
          const resp = await this.$http.get('/api/v1/scrcpy/stream/ws-url', {
            params: { device_id: this.deviceId }
          })
          const data = resp?.data
          // 设备忙占用：先断开再重试一次
          if (data && data.success === false && data.code === 'device_busy') {
            try {
              await this.$http.post(`/api/v1/scrcpy/disconnect/${encodeURIComponent(this.deviceId)}`)
              await new Promise(r => setTimeout(r, 500))
              const retry = await this.$http.get('/api/v1/scrcpy/stream/ws-url', { params: { device_id: this.deviceId, preempt: true } })
              if (retry?.data?.wss_url) {
                wsUrl = retry.data.wss_url
              }
            } catch (_) { }
          }
          if (!wsUrl) {
            if (!data || !data.wss_url) throw new Error('invalid ws-url response')
            wsUrl = data.wss_url
          }
          if (wsUrl.startsWith('/')) {
            wsUrl = `${protocol}//${host}${wsUrl}`
          }
        } catch (e) {
          console.warn('⚠️ 获取签名WS地址失败，终止连接:', e)
          this.connectionStatus = 'error'
          return
        }
      }

      console.log('📡 连接到:', wsUrl)

      this.websocket = new WebSocket(wsUrl)
      this.websocket.binaryType = 'arraybuffer'

      // 优化：设置WebSocket缓冲区大小
      if (this.websocket.bufferedAmount !== undefined) {
        this.websocket.bufferedAmountLowThreshold = 1024 * 64 // 64KB
      }

      this.websocket.onopen = () => {
        console.log('✅ WebSocket connected successfully')

        // 🔧 保持连接中状态，等待第一帧画面到达后才切换到已连接
        // this.connectionStatus = 'connected' // 注释掉，改为在收到第一帧后设置
        console.log('⏳ WebSocket已连接，等待第一帧画面...')
        // 🆕 记录连接时间并初始化首帧状态，避免冷启动误判
        this.wsOpenAt = Date.now()
        this.lastFrameTime = this.wsOpenAt
        this.hasReceivedAnyFrame = false

        // 🔧 设置超时保护：如果10秒内还没收到第一帧，强制切换状态避免一直加载
        const firstFrameTimeout = setTimeout(() => {
          if (!this.hasReceivedAnyFrame && this.connectionStatus === 'connecting') {
            console.warn('⚠️ 10秒内未收到第一帧，强制切换到已连接状态')
            this.connectionStatus = 'connected'
            this.hasReceivedAnyFrame = true // 标记为已接收，避免后续重复切换
          }
        }, 10000)
        // 保存timeout ID以便清理
        this._firstFrameTimeout = firstFrameTimeout

        // 🆕 连接成功后，立即和定期请求配置帧
        this.requestConfigFrames()
        this.startIFrameRequestTimer()
        // 🆕 启动主动保活（每30s向服务器发送一次keepalive）
        try {
          if (this.keepaliveTimer) clearInterval(this.keepaliveTimer)
          this.keepaliveTimer = setInterval(() => {
            try {
              if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
                this.websocket.send(JSON.stringify({ type: 'keepalive', ts: Date.now() }))
              }
            } catch (_) { }
          }, 30000)
        } catch (_) { }

        this.reconnectAttempts = 0 // 重置重连计数

        // 🔧 强制Vue重新渲染，确保UI更新
        this.$forceUpdate()
      }

      this.websocket.onmessage = (event) => {
        this.handleWebSocketMessage(event)
      }

      this.websocket.onclose = (event) => {
        console.log('WebSocket disconnected, code:', event.code, 'reason:', event.reason)
        this.connectionStatus = 'disconnected'

        // 🆕 停止I帧请求定时器
        this.stopIFrameRequestTimer()
        try { if (this.keepaliveTimer) { clearInterval(this.keepaliveTimer); this.keepaliveTimer = null } } catch (_) { }

        // 🆕 如果后端是正常关闭(1000)或空闲超时(reason包含 idle timeout)，立即做彻底清理并通知父组件
        const isNormalClose = event.code === 1000
        const isIdleTimeout = typeof event.reason === 'string' && event.reason.includes('idle timeout')
        const isStallTimeout = typeof event.reason === 'string' && event.reason.includes('stall timeout')
        if (isNormalClose || isIdleTimeout) {
          try { this.cleanup() } catch { }
          try { this.$emit && this.$emit('stream-closed', { code: event.code, reason: event.reason }) } catch { }
          return
        }

        // 🆕 如果是系统键保护期内断开，延长重连延迟
        // stall timeout 走指数退避更稳
        let reconnectDelay = this.systemKeyProtection ? 3000 : 2000
        if (isStallTimeout) {
          // 2s, 3s, 5s, 8s, 13s, ...（上限 15s）
          const seq = [2000, 3000, 5000, 8000, 13000, 15000]
          reconnectDelay = seq[Math.min(this.reconnectAttempts, seq.length - 1)]
        }
        if (this.systemKeyProtection) {
          console.log('🔑 系统键保护期内断开，延长重连延迟到', reconnectDelay, 'ms')
        }

        // 🆕 只有在允许自动重连且非正常关闭时才重连
        if (this.shouldAutoReconnect && this.reconnectAttempts < this.maxReconnectAttempts) {
          this.reconnectAttempts++
          console.log(`尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts})...`)
          setTimeout(() => {
            if (this.shouldAutoReconnect) {
              this.connectWebSocket()
            }
          }, reconnectDelay)
        } else if (!this.shouldAutoReconnect) {
          console.log('自动重连已禁用')
        } else {
          console.log('达到最大重连次数，停止重连')
          this.shouldAutoReconnect = false
          this._toastOnce('reconnect-failed', '多次重连失败，请刷新页面或重新打开', 'error', 20000)
          try { this.$emit && this.$emit('stream-closed', { code: event.code, reason: event.reason }) } catch { }
        }
      }

      this.websocket.onerror = (error) => {
        console.error('WebSocket error:', error)
        this.connectionStatus = 'error'
      }

      // 设置连接超时
      setTimeout(() => {
        if (this.websocket && this.websocket.readyState === WebSocket.CONNECTING) {
          console.warn('WebSocket连接超时')
          this.websocket.close()
          this.connectionStatus = 'error'
        }
      }, 10000) // 10秒超时
    },

    // 🆕 启动I帧请求定时器
    startIFrameRequestTimer() {
      // 🆕 每2秒请求一次I帧，更频繁确保JMuxer能收到关键帧
      this.iFrameTimer = setInterval(() => {
        if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
          try {
            // 发送多种请求I帧和配置帧的控制消息
            const requests = [
              { msg_type: 100, action: 'request_iframe' },
              { msg_type: 101, action: 'request_config_frames' }
            ]

            requests.forEach(request => {
              this.websocket.send(JSON.stringify(request))
            })

            console.log('🎯 已请求I帧和配置帧')
          } catch (error) {
            console.error('❌ 请求I帧失败:', error)
          }
        }
      }, 2000) // 每2秒请求一次

      console.log('⏰ I帧请求定时器已启动（每2秒）')
    },

    // 🆕 停止I帧请求定时器
    stopIFrameRequestTimer() {
      if (this.iFrameTimer) {
        clearInterval(this.iFrameTimer)
        this.iFrameTimer = null
        console.log('⏰ I帧请求定时器已停止')
      }
    },

    // 🆕 请求配置帧的控制消息
    requestConfigFrames() {
      if (this.websocket && this.websocket.readyState === WebSocket.OPEN) {
        // 发送请求配置帧的控制消息
        const configFrameRequest = {
          msg_type: 101, // 自定义消息类型，请求配置帧
          action: 'request_config_frames'
        }
        this.websocket.send(JSON.stringify(configFrameRequest))
        console.log('🎯 已请求配置帧')
      }
    },

    cleanup() {
      console.log('🧹 清理SCRCPY组件资源...')

      // 🆕 禁用自动重连
      this.shouldAutoReconnect = false

      // 🆕 停止I帧请求定时器
      this.stopIFrameRequestTimer()
      // 🆕 停止无帧检测定时器
      try { if (this.stallTimer) { clearInterval(this.stallTimer); this.stallTimer = null } } catch (e) { }
      // 🎥 停止录制状态轮询
      try { if (this.recordingStatusTimer) { clearInterval(this.recordingStatusTimer); this.recordingStatusTimer = null } } catch (e) { }
      // 🆕 清理初始化兜底计时器
      try { if (this._initInputFallbackTimer) { clearTimeout(this._initInputFallbackTimer); this._initInputFallbackTimer = null } } catch (e) { }
      // 🔧 清理第一帧超时计时器
      try { if (this._firstFrameTimeout) { clearTimeout(this._firstFrameTimeout); this._firstFrameTimeout = null } } catch (e) { }

      if (this.websocket) {
        // 使用1000关闭码表示正常关闭
        this.websocket.close(1000, 'Component cleanup')
        this.websocket = null
        console.log('🔌 WebSocket连接已关闭 (正常关闭)')
      }
      try { if (this.keepaliveTimer) { clearInterval(this.keepaliveTimer); this.keepaliveTimer = null } } catch (_) { }

      // 🆕 移除全局键盘事件监听
      document.removeEventListener('keydown', this.handleKeyDown)
      document.removeEventListener('keyup', this.handleKeyUp)
      console.log('⌨️ 全局键盘事件监听器已移除')

      // 🔧 移除激活事件监听器
      window.removeEventListener('scrcpy-component-activated', this.handleOtherComponentActivated)

      // 🔧 从renderElement移除激活事件监听
      if (this.renderElement) {
        this.renderElement.removeEventListener('click', this.activateComponent)
        this.renderElement.removeEventListener('mousedown', this.activateComponent)
        this.renderElement.removeEventListener('touchstart', this.activateComponent)
      }

      // 🆕 清理交互处理器
      try {
        if (this.interactionHandler && typeof this.interactionHandler.unbind === 'function') {
          this.interactionHandler.unbind()
        }
      } catch (_) { }
      this.interactionHandler = null

      // 🆕 解绑 video 上的快捷键/粘贴事件
      try {
        const v = this.$refs.videoContainer?.querySelector('video')
        if (v) {
          if (this._videoKeyHandler) v.removeEventListener('keydown', this._videoKeyHandler)
          if (this._videoPasteHandler) v.removeEventListener('paste', this._videoPasteHandler)
        }
      } catch (_) { }
      this._videoKeyHandler = null
      this._videoPasteHandler = null

      // 清理 WebCodecs 解码器
      try {
        if (this.wcDecoder) {
          this.wcDecoder.close()
        }
      } catch (_) { }
      this.wcDecoder = null
      this.decoderConfigured = false
      this.codecDescription = null

      // 清除渲染上下文
      this.renderElement = null
      this.wcCanvas = null
      this.wcCtx = null

      console.log('⌨️ 全局键盘事件监听器已移除')

      this.connectionStatus = 'disconnected'
      this.reconnectAttempts = 0

      console.log('✅ SCRCPY组件资源清理完成')
    },

    // 🆕 处理窗口大小改变（包括键盘弹出/收起）
    handleWindowResize() {
      console.log('📏 窗口大小改变')
      // InteractionHandler 会自动处理坐标转换

      // 🔧 修复：移动端键盘弹出导致画面拉伸的问题
      try {
        const container = this.$refs.videoContainer
        const canvas = this.wcCanvas || this.renderElement
        if (container && canvas) {
          // 强制重新计算 canvas 尺寸，适应新的容器大小
          this.$nextTick(() => {
            try {
              const rect = container.getBoundingClientRect()
              if (rect.width > 0 && rect.height > 0) {
                // canvas 尺寸由 CSS 控制，这里只需要触发重绘
                console.log('📐 重新计算画面尺寸:', rect.width, 'x', rect.height)
              }
            } catch (e) {
              console.warn('⚠️ 画面尺寸重计算失败:', e)
            }
          })
        }
      } catch (e) {
        console.warn('⚠️ handleWindowResize 错误:', e)
      }
    },

    // 🔧 处理页面可见性变化 - 解决后台切换回来画面不更新问题
    handleVisibilityChange() {
      this.isPageHidden = document.hidden
    },

    // 🔧 处理其他组件激活事件
    handleOtherComponentActivated(event) {
      // 如果激活的不是当前组件，取消当前组件的激活状态
      if (event.detail && event.detail.componentInstance !== this) {
        this.deactivateComponent()
      }
    },


    // 🆕 简单的提示节流：相同key在冷却期内只提示一次
    _toastOnce(key, message, type = 'info', cooldownMs = 8000) {
      try {
        const now = Date.now()
        const last = this._lastToastAt[key] || 0
        if (now - last < cooldownMs) return
        this._lastToastAt[key] = now
        if (!this.$message) return
        if (type === 'error') this.$message.error(message)
        else if (type === 'warning') this.$message.warning(message)
        else this.$message.info(message)
      } catch (_) { }
    },

    // 🔧 激活当前组件（多窗口焦点管理）
    activateComponent() {
   
   
      
      // 如果组件已经激活，无需重复操作
      if (this.isComponentActive) {
           //如果触发了点击, timer 就重新计算
           setTimeout(() => {
          this.$emit('activateComponent')
           }, 2000)
        return
      }

      this.isComponentActive = true
      console.log('✨ 组件已激活，设备ID:', this.deviceId)

      // 通知父组件或全局事件总线，让其他ScrcpyScreen实例取消激活
      // 使用自定义事件通知其他组件
      const event = new CustomEvent('scrcpy-component-activated', {
        detail: { deviceId: this.deviceId, componentInstance: this }
      })
      window.dispatchEvent(event)

      // 🔧 显示轻量级提示，告知用户已激活
      this._toastOnce('component-activated', `已激活设备 ${this.deviceId}`, 'info', 3000)
    },

    // 🔧 取消激活当前组件
    deactivateComponent() {
      if (!this.isComponentActive) {
        return
      }

      this.isComponentActive = false
      console.log('💤 组件已取消激活，设备ID:', this.deviceId)
    },

    // 🎥 录制控制
    toggleRecording() {
      if (this.recording) {
        this._guardReadOnly(() => this.startStopRecording(false))
      } else {
        this._guardReadOnly(() => this.startStopRecording(true))
      }
    },
    startStopRecording(start) {
      try {
        if (!this.websocket || this.websocket.readyState !== WebSocket.OPEN) {
          this.$message && this.$message.error('未连接，无法控制录制')
          return
        }
        const reqId = start ? this.wsRecordStart() : this.wsRecordStop()
        if (reqId) {
          this.pendingCmds[reqId] = start ? 'recordStart' : 'recordStop'
        }
      } catch (e) {
        this.$message && this.$message.error('录制控制失败: ' + (e && e.message || e))
      }
    },
    async fetchRecordingStatus() {
      try {
        // 优先使用服务端确认的deviceId
        const key = (this.readyInfo && this.readyInfo.deviceId) || this.deviceId
        if (!key) return
        const { data } = await this.$http.get('/api/v1/scrcpy/status')
        const map = data && data.devices
        if (!map) return
        const dev = map[key]
        if (dev && Object.prototype.hasOwnProperty.call(dev, 'recording')) {
          this.recording = !!dev.recording
        }
      } catch (_) { /* 忽略轮询错误 */ }
    },

    // 🔧 从推流截图并下载（用于制作pHash模板）
    async takeAndDownloadScreenshot() {
      try {
        const canvas = this.renderElement
        if (!canvas) {
          this.$message.warning('画面未准备好，无法截图')
          return
        }
        const offscreen = this.screenshotCanvas || document.createElement('canvas')
        this.screenshotCanvas = offscreen
        offscreen.width = canvas.width
        offscreen.height = canvas.height
        const ctx = offscreen.getContext('2d')
        if (!ctx) {
          this.$message.error('无法创建截图上下文')
          return
        }
        ctx.drawImage(canvas, 0, 0, offscreen.width, offscreen.height)
        const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
        const filename = `bd-cloudphone-screenshot-${offscreen.width}x${offscreen.height}-${timestamp}.png`
        offscreen.toBlob((blob) => {
          if (!blob) {
            this.$message.error('截图失败')
            return
          }
          const url = URL.createObjectURL(blob)
          const link = document.createElement('a')
          link.href = url
          link.download = filename
          document.body.appendChild(link)
          link.click()
          document.body.removeChild(link)
          URL.revokeObjectURL(url)
          this.lastScreenshotInfo = {
            size: `${offscreen.width}×${offscreen.height}`,
            filename,
            timestamp: new Date().toLocaleString()
          }
          this.$message.success(`截图已下载: ${filename}`)
        }, 'image/png', 1.0)
      } catch (error) {
        console.error('❌ 截图下载失败:', error)
        this.$message.error('截图下载失败: ' + error.message)
      }
    },
    // 🆕 拖拽进入/离开/释放
    onScanDragEnter() {
      this.isScanDragOver = true
    },
    onScanDragLeave() {
      this.isScanDragOver = false
    },
    async onScanDrop(e,formData) {
        let forms = formData
      try {
        // if(!this.accountId) {
        //   this.$message.warning('请先选择账号打开云机')
        //   return
        // }
        if(e=='copy') {
           data = await scanAuto(forms)
            if (data && data.code ==200) {
              this.$message.success('自动上号成功')
            } 
            return
        }
        this.isScanDragOver = false
        const files = e.dataTransfer?.files
        if (!files || !files.length) return
        const file = files[0]
        if (!/\.png$|\.jpe?g$/i.test(file.name)) {
          this.$message.error('仅支持PNG/JPEG')
          return
        }
        if (file.size > 1 * 1024 * 1024) { // 与后端一致：1MB
          this.$message.error('图片不能超过1MB')
          return
        }
        const form = new FormData()
        form.append('file', file)
         form.append('accountId', this.accountId)
     
        let data; // 提前声明变量
           data = await scanAuto(form)
        if (data && data.code ==200) {
          this.$message.success('自动上号成功')
        } else {
          // this.$message.error(data?.message || '上传或写入失败')
        }
      } catch (err) {
        // this.$message.error(err?.response?.data?.detail || err.message)
      }
    },
    // 🆕 触发上传二维码图片并写入摄像头
    triggerScanUpload(types) {
   try {
    // 每次点击都创建新的 input，避免事件锁定
    const input = document.createElement('input')
    input.type = 'file'
    input.accept = 'image/png,image/jpeg,image/jpg'
    input.style.display = 'none'
    input.addEventListener('change', async (e) => {
      const file = e.target.files && e.target.files[0]
      if (!file) return
      if (file.size > 1 * 1024 * 1024) {
        this.$message.error('图片不能超过1MB')
        input.value = ''
        return
      }
      try {
        const pngFile = await convertImageToPNG(file)
        const form = new FormData()
        form.append('file', pngFile)
        form.append('accountId', this.accountId)
        let data
        if (types === 'scan') {
          data = await scanCode(form)
           if (data.code == 200) {
          this.$message.success('已上传并写入摄像头')
        } 
        } else {
          data = await scanAuto(form)
           if (data.code == 200) {
          //  yunjiClose(this.accountId);
            this.$message.success('自动上号成功')
          } else {
              // yunjiClose(this.accountId);
            // this.$message.error('上传或写入失败')
          }
        }
       
      } catch (err) {
        // this.$message.error(err?.response?.data?.detail || err.message)
      } finally {
        input.value = ''
        // 用完后移除 input，避免内存泄漏
        document.body.removeChild(input)
      }
    })
    document.body.appendChild(input)
    input.click()
  } catch (e) {
    console.error('triggerScanUpload error:', e)
    this.$message.error('打开文件选择器失败')
  }
    }
  }
}
</script>

<style scoped>
.scrcpy-screen {
  width: 100%;
  height: 98%;
  /* 🔧 从550px改为520px，匹配新容器高度 */
  position: relative;
  background: transparent;
  border-radius: 0px;
  /* 🔧 移除圆角，完全贴边 */
  overflow: hidden;
  margin: 0;
  /* 🔧 移除边距 */
  padding: 0;
  /* 🔧 移除内边距 */
}

.hidden-input {
  position: absolute;
  top: -9999px;
  left: -9999px;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
  border: none;
  outline: none;
  background: transparent;
  z-index: -1;
  /* 确保不会影响页面滚动 */
  overflow: hidden;
  resize: none;
  user-select: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
}

.main-containers {
  display: flex;
  flex-direction: column;
  /* 🔧 改为垂直排列 */
  height: 100%;
  /* 🔧 从550px改为520px */
  gap: 0;
  /* 移除间隙，让上下紧贴 */
  margin: 0;
  /* 🔧 移除边距 */
  padding: 0;
  /* 🔧 移除内边距 */
  box-sizing: border-box;
  /* 🔧 确保正确的盒模型 */
}

.video-section {
  width: 100%;
  height: 100%;
  /* background: #000; */
  /* 黑色背景 */
  /* border-radius: 10px; 移除圆角，贴合边框 */
  overflow: hidden;
  display: flex;
  justify-content: center;
  align-items: center;
  /* 性能优化 */
  transform: translateZ(0);
  /* 强制硬件加速 */
  backface-visibility: hidden;
  will-change: transform;
  /* 🔧 移除固定高度，让它自适应 */
  position: relative;
  /* 🔧 确保绝对定位的子元素相对于此定位 */
  margin: 0;
  /* 🔧 去掉左右边距，紧贴左边 */
  /* border: 2px solid #6C5DD3 */
}

.video-container {
  width: 100%;
  height: 100%;
  position: relative;
  border-radius: 14px;
  /* 🔧 移除圆角，完全贴边 */
  overflow: hidden;
  cursor: pointer;
  /* 性能优化 */
  transform: translateZ(0);
  /* 强制硬件加速 */
  backface-visibility: hidden;
  will-change: transform;
  display: flex;
  /* 🔧 添加flex布局 */
  justify-content: center;
  /* 🔧 水平居中 */
  align-items: center;
  /* 🔧 垂直居中 */
  /* 🔧 减少容器边距 */
  margin: 0;
  /* 🔧 移除所有边距 */
  padding: 0;
  /* 🔧 移除所有内边距 */
}

.video-container canvas {
  width: 100% !important;
  height: 100% !important;
  /* 🔧 改为100%，完全填充 */
  max-width: 100% !important;
  /* 🔧 允许填充宽度 */
  max-height: 100% !important;
  /* 🔧 允许填充高度 */
  object-fit: fill;
  /* 🔧 改为fill，避免黑边 */
  display: block;
  border-radius: 0px;
  /* 🔧 移除圆角 */
  /* background: #000; */
  margin: 0 auto;
}

.video-container video {
  width: 100%;
  /* 🔧 从70%改为100%，与JavaScript设置一致 */
  height: 100%;
  /* 🔧 从70%改为100%，与JavaScript设置一致 */
  max-width: 100%;
  /* 🔧 从70%改为100% */
  max-height: 100%;
  /* 🔧 从70%改为100% */
  object-fit: fill;
  /* 🔧 改为fill，完全填充容器，避免黑边 */
  /* background: #000; */
}

.control-bottom-panel {
  width: 100%;
  /* 占满整个底部面板 */
  height: 45px;
  /* 🔧 从50px进一步缩小到45px */
  padding: 3px;
  /* 🔧 从4px减少到3px */
  background: #ffffff;
  /* 纯白色背景 */

  display: flex;
  flex-direction: row;
  /* 🔧 改为水平排列 */
  justify-content: center;
  /* 🔧 改为居中对齐，因为只有一组按钮 */
  align-items: center;
  /* 🔧 垂直居中 */
  gap: 8px;
  /* 🔧 从10px减少到8px */
  color: #333;
  font-size: 10px;
  /* 🔧 从7px增加到10px */
  overflow-x: auto;
  /* 🔧 水平滚动 */
}

.control-section {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 2px;
  /* 🔧 从1px增加到2px */
  background: transparent;
  /* 🔧 透明背景 */
  /* 🔧 调整为适应底部水平布局 */

  height: 30px;
  /* �� 从40px缩小到35px */
}

/* 🆕 拖拽高亮样式 */
.scan-upload-section {
  width: 100%;
  height: 100%;
  min-height: 0; /* 防止flex布局撑高 */
  transition: box-shadow 0.2s ease, border-color 0.2s ease, background 0.2s ease;
}

.scan-upload-section.drag-over {
  border-color: #409eff;
  box-shadow: inset 0 0 0 2px rgba(64, 158, 255, 0.25);
  background: rgba(64, 158, 255, 0.06);
}

.control-buttons-vertical {
  display: flex;
  flex-direction: row;
  /* 🔧 改为水平排列 */
  justify-content: center;
  align-items: center;
  /* 🔧 垂直居中 */
  gap: 2px;
  /* 🔧 从3px减少到2px，让按钮更紧凑 */
  margin: 0;
  /* 移除边距 */
  width: 100%;
  /* 确保按钮组占据全宽 */
  flex-wrap: wrap;
  /* 🔧 允许换行 */
}

.control-btn-small {
  /* 🔧 调整按钮尺寸适应底部布局 */
  width: 32px;
  /* 🔧 从35px缩小到32px */
  height: 24px;
  /* 🔧 从14px大幅增加到24px */
  border-radius: 3px;
  /* 从1px增加到3px */
  background: transparent;
  /* 🔧 透明背景 */
  color: #666;
  /* 🔧 灰色图标 */
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  /* 🔧 从9px大幅增加到14px */
  cursor: pointer;
  transition: all 0.2s ease;
  border: 1px solid #ddd;
  /* 🔧 边框1px */
  outline: none;
  box-shadow: none;
  /* 🔧 移除阴影 */
  padding: 0;
  /* 移除内边距 */
}

.control-btn-small:hover {
  background: #f8f9fa;
  /* 🔧 悬停时只显示淡灰色背景 */
  color: #333;
  /* 🔧 悬停时图标变深 */
  border-color: #bbb;
  /* 🔧 边框变深 */
}

.control-btn-small:active {
  background: #e9ecef;
  /* 🔧 点击时显示稍深背景 */
  transform: scale(0.98);
  /* 🔧 点击时轻微缩放 */
}

/* 🔧 移除所有按钮的特定颜色样式，统一使用透明背景 */
.control-btn-small.home-btn,
.control-btn-small.back-btn,
.control-btn-small.menu-btn,
.control-btn-small.recent-btn {
  /* 🚫 移除剪贴板和文本按钮样式
  .control-btn-small.copy-btn,
  .control-btn-small.paste-btn,
  .control-btn-small.send-btn
  */
  background: transparent;
  /* 统一透明背景 */
  color: #666;
  /* 统一图标颜色 */
}

.control-btn-small.home-btn:hover,
.control-btn-small.back-btn:hover,
.control-btn-small.menu-btn:hover,
.control-btn-small.recent-btn:hover {
  /* 🚫 移除剪贴板和文本按钮样式
  .control-btn-small.copy-btn:hover,
  .control-btn-small.paste-btn:hover,
  .control-btn-small.send-btn:hover
  */
  background: #f8f9fa;
  /* 统一悬停背景 */
  color: #333;
  /* 统一悬停图标颜色 */
}

/* ✨ 连接状态相关样式 */

/* 🔧 状态叠加层 - 覆盖整个视频区域并居中显示内容 */
.status-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.8);
  /* 半透明黑色背景 */
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 10;
  /* 确保在视频之上 */
  border-radius: 0px;
  /* 与容器保持一致 */
}

.connecting,
.disconnected,
.error {
  text-align: center;
  padding: 40px 20px;
  background: rgba(0, 0, 0, 0.6);
  /* 内容区域的背景 */
  border-radius: 8px;
  min-width: 200px;
  max-width: 280px;
  /* 🔧 限制最大宽度 */
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
  /* 🔧 添加阴影增强视觉层次 */
}

.connecting {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

.play-icon {
  font-size: 48px;
  margin-bottom: 16px;
  color: #409eff;
  cursor: pointer;
  transition: all 0.3s ease;
}

.play-icon:hover {
  color: #66b1ff;
  transform: scale(1.1);
}

.error-icon {
  font-size: 48px;
  margin-bottom: 16px;
  color: #f56c6c;
  cursor: pointer;
  transition: all 0.3s ease;
}

.error-icon:hover {
  color: #f78989;
  transform: scale(1.1);
}

.loading-spinner {
  width: 60px;
  /* 🔧 稍微缩小尺寸适应紧凑布局 */
  height: 60px;
  border: 4px solid rgba(255, 255, 255, 0.1);
  /* 🔧 调整背景圆环透明度 */
  border-top: 4px solid #409eff;
  /* 🔧 增加边框粗细，更明显 */
  border-right: 4px solid #67c23a;
  border-bottom: 4px solid #e6a23c;
  /* 🔧 添加底部颜色 */
  border-left: 4px solid #f56c6c;
  /* 🔧 添加左侧颜色，形成完整彩虹 */
  border-radius: 50%;
  animation: rainbow-spin 0.3s linear infinite, pulse-glow 0.2s ease-in-out infinite;
  /* 🔧 加快转速，添加脉冲效果 */
  margin: 0 auto 16px;
  /* 🔧 减少底部边距 */
  box-shadow: 0 4px 12px rgba(64, 158, 255, 0.4);
  /* 🔧 增强阴影效果 */
  /* 🔧 添加更平滑的动画效果 */
  transform-origin: center;
  will-change: transform;
  /* 🔧 优化动画性能 */
}

.connecting-tips {
  font-size: 12px !important;
  color: #ccc !important;
  /* 🔧 改为与叠加层背景匹配的颜色 */
  margin-top: 8px !important;
  opacity: 0.8;
}

@keyframes spin {
  0% {
    transform: rotate(0deg);
  }

  100% {
    transform: rotate(360deg);
  }
}

@keyframes rainbow-spin {
  0% {
    transform: rotate(0deg);
    border-top-color: #409eff;
    border-right-color: #67c23a;
    border-bottom-color: #e6a23c;
    border-left-color: #f56c6c;
  }

  25% {
    transform: rotate(90deg);
    border-top-color: #67c23a;
    border-right-color: #e6a23c;
    border-bottom-color: #f56c6c;
    border-left-color: #409eff;
  }

  50% {
    transform: rotate(180deg);
    border-top-color: #e6a23c;
    border-right-color: #f56c6c;
    border-bottom-color: #409eff;
    border-left-color: #67c23a;
  }

  75% {
    transform: rotate(270deg);
    border-top-color: #f56c6c;
    border-right-color: #409eff;
    border-bottom-color: #67c23a;
    border-left-color: #e6a23c;
  }

  100% {
    transform: rotate(360deg);
    border-top-color: #409eff;
    border-right-color: #67c23a;
    border-bottom-color: #e6a23c;
    border-left-color: #f56c6c;
  }
}

/* 🔧 添加脉冲发光动画 */
@keyframes pulse-glow {
  0% {
    box-shadow: 0 4px 12px rgba(64, 158, 255, 0.4);
    transform: scale(1);
  }

  50% {
    box-shadow: 0 4px 20px rgba(64, 158, 255, 0.8), 0 0 30px rgba(103, 194, 58, 0.6);
    transform: scale(1.05);
  }

  100% {
    box-shadow: 0 4px 12px rgba(64, 158, 255, 0.4);
    transform: scale(1);
  }
}

.status-overlay p {
  margin: 0;
  font-size: 14px;
  color: #ccc;
  /* �� 确保文字在深色背景上可见 */
}

.disconnected:hover,
.error:hover {
  background: rgba(0, 0, 0, 0.9);
}

.disconnected:hover .play-icon {
  color: #66b1ff;
}

.error:hover .error-icon {
  color: #f78989;
}

/* 🔧 响应式设计 - 小屏幕适配 */
@media (max-width: 480px) {

  .connecting,
  .disconnected,
  .error {
    padding: 30px 15px;
    min-width: 160px;
    max-width: 240px;
  }

  .loading-spinner {
    width: 50px;
    height: 50px;
  }

  .play-icon,
  .error-icon {
    font-size: 40px;
  }
}

/* 🔧 截图按钮样式 */
.screenshot-btn {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
  border: 2px solid transparent !important;
  color: white !important;
  font-size: 16px !important;
  transition: all 0.3s ease !important;
}

.screenshot-btn:hover {
  background: linear-gradient(135deg, #764ba2 0%, #667eea 100%) !important;
  transform: scale(1.05) !important;
  box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4) !important;
}

.screenshot-btn:active {
  transform: scale(0.95) !important;
}

.screenshot-info {
  margin-top: 4px;
  text-align: center;
  opacity: 0.8;
  font-size: 10px;
  color: #666;
}

.screenshot-info small {
  background: rgba(0, 0, 0, 0.1);
  padding: 2px 4px;
  border-radius: 3px;
  display: inline-block;
}

.imgs {
  width: 32px;
  margin-right: 10px;
  cursor: pointer;
}

.loading-box {
  position: absolute;
  z-index: 85;
  width: 100%;
  height: 100%;
}

.scan-box {
  position: relative;

}

.scan-tips-cont {
  width: 600px;
  object-fit: contain;
}

.scan-tips {
  width: 22px;
  position: absolute;
  top: -6px;
  right: -10px;
}
</style>
